package org.etnaframework.module.base.utils.notify;

import org.etnaframework.module.base.utils.DbMap;
import org.etnaframework.module.base.utils.HttpClientUtils;
import org.etnaframework.module.base.utils.JacksonUtils;
import org.slf4j.MDC;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.lang.management.ManagementFactory;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.util.Map;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 钉钉机器人发送辅助工具类
 *
 * <a href="https://open.dingtalk.com/document/robots/custom-robot-access/">官方文档</a>
 *
 * @author jasyaf
 * @since 2023-09-02
 */
public class DingTalkRobotUtils {

    private static final int TIMEOUT_SEC = 30;

    private static final ThreadPoolExecutor executor = new ThreadPoolExecutor(0, 1000, 60L, TimeUnit.SECONDS, new SynchronousQueue<>());

    /**
     * 钉钉机器人升级，增加了安全机制，这里实现了加签模式，根据钉钉机器人的签名算法计算签名
     * <a href="https://open.dingtalk.com/document/robots/customize-robot-security-settings">官方文档</a>
     * <p>
     * 如果没有开启【加签】，传参方式为 机器人URL
     * 如果开启了【加签】，传参方式为 密钥,机器人URL
     */
    private static String _genSignedUrl(String robotUrl) {
        if (null == robotUrl || robotUrl.isEmpty()) {
            return "";
        }
        String[] split = robotUrl.split(",");
        if (split.length == 2) {
            String secret = split[0];
            String url = split[1];
            long timestamp = System.currentTimeMillis();
            String stringToSign = timestamp + "\n" + secret;
            try {
                Mac mac = Mac.getInstance("HmacSHA256");
                mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
                byte[] signData = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
                String sign = URLEncoder.encode(new String(Base64.getEncoder().encode(signData)), StandardCharsets.UTF_8.name());
                return url + "&timestamp=" + timestamp + "&sign=" + sign;
            } catch (Throwable ignore) {
            }
        }
        return robotUrl;
    }

    /**
     * 发送纯文本消息，可通过手机号指定@谁（同步发送）
     *
     * @param robotUrl 如果是加签安全设置的机器人，传入格式为 密钥,机器人URL
     * @param at       需要at的人，如果想要at全员，请传入all
     */
    public static void sendText(String robotUrl, CharSequence content, String... at) {
        if (robotUrl == null || robotUrl.isEmpty() || null == content) { // 地址或内容为空时直接忽略
            return;
        }
        // {
        //     "msgtype":"text",
        //     "text":{
        //         "content": "我就是我, 是不一样的烟火"
        //     },
        //     "at":{
        //         "atMobiles": [
        //             "156xxxx8827",
        //             "189xxxx8325"
        //         ],
        //         "isAtAll": false
        //     }
        // }
        DbMap data = new DbMap();
        data.put("msgtype", "text");
        data.put("text", new DbMap().append("content", content));
        data.put("at", new DbMap().append("atMobiles", Arrays.asList(at)).append("isAtAll", at.length == 1 && "all".equals(at[0])));
        HttpClientUtils.post(_genSignedUrl(robotUrl)).timeout(TIMEOUT_SEC, TimeUnit.SECONDS).header("Content-Type", "application/json").content(JacksonUtils.toJson(data)).fetch();
    }

    /**
     * 发送纯文本消息，可通过手机号指定@谁（异步发送）
     *
     * @param robotUrl 如果是加签安全设置的机器人，传入格式为 密钥,机器人URL
     * @param at       需要at的人，如果想要at全员，请传入all
     */
    public static void sendTextAsync(String robotUrl, CharSequence content, String... at) {
        Map<String, String> mdc = MDC.getCopyOfContextMap();
        executor.execute(() -> {
            try {
                if (null != mdc) {
                    MDC.setContextMap(mdc);
                }
                sendText(robotUrl, content, at);
            } finally {
                MDC.clear();
            }
        });
    }

    /**
     * 发送markdown消息，可通过手机号指定@谁，注意如果要@指定人需要在content中加入@手机号（同步发送）
     * <p>
     * 这里会原样发出content的内容，如果需要对机器人推送的消息做定制请使用本接口，一般情况用{@link #sendTextWithTitle(String, String, CharSequence, String...)}即可
     *
     * @param robotUrl 如果是加签安全设置的机器人，传入格式为 密钥,机器人URL
     * @param at       需要at的人，如果想要at全员，请传入all
     */
    public static void sendMarkdown(String robotUrl, String title, CharSequence content, String... at) {
        if (robotUrl == null || robotUrl.isEmpty() || null == content) { // 地址或内容为空时直接忽略
            return;
        }
        // {
        //     "msgtype":"markdown",
        //     "markdown":{
        //         "title":"杭州天气",
        //         "text": "#### 杭州天气 @156xxxx8827\n" +
        //                 "> 9度，西北风1级，空气良89，相对温度73%\n\n" +
        //                 "> ![screenshot](http://image.jpg)\n"  +
        //                 "> ###### 10点20分发布 [天气](http://www.thinkpage.cn/) \n"
        //     },
        //     "at":{
        //         "atMobiles": [
        //             "156xxxx8827",
        //             "189xxxx8325"
        //         ],
        //         "isAtAll":false
        //     }
        // }
        DbMap data = new DbMap();
        data.put("msgtype", "markdown");
        data.put("markdown", new DbMap().append("title", title).append("text", content));
        data.put("at", new DbMap().append("atMobiles", Arrays.asList(at)).append("isAtAll", at.length == 1 && "all".equals(at[0])));
        HttpClientUtils.post(_genSignedUrl(robotUrl)).timeout(TIMEOUT_SEC, TimeUnit.SECONDS).header("Content-Type", "application/json").content(JacksonUtils.toJson(data)).fetch();
    }

    /**
     * 发送markdown消息，可通过手机号指定@谁，注意如果要@指定人需要在content中加入@手机号（异步发送）
     * <p>
     * 这里会原样发出content的内容，如果需要对机器人推送的消息做定制请使用本接口，一般情况用{@link #sendTextWithTitle(String, String, CharSequence, String...)}即可
     *
     * @param robotUrl 如果是加签安全设置的机器人，传入格式为 密钥,机器人URL
     * @param at       需要at的人，如果想要at全员，请传入all
     */
    public static void sendMarkdownAsync(String robotUrl, String title, CharSequence content, String... at) {
        Map<String, String> mdc = MDC.getCopyOfContextMap();
        executor.execute(() -> {
            try {
                if (null != mdc) {
                    MDC.setContextMap(mdc);
                }
                sendMarkdown(robotUrl, title, content, at);
            } finally {
                MDC.clear();
            }
        });
    }

    /**
     * 发送带标题的文本消息（内部使用markdown包装），可通过手机号指定@谁，通用格式，注意如果要@指定人需要在content中加入@手机号（同步发送）
     * <p>
     * 如果需要对content内容定制，请使用{@link #sendMarkdown(String, String, CharSequence, String...)}
     *
     * @param robotUrl 如果是加签安全设置的机器人，传入格式为 密钥,机器人URL
     * @param at       需要at的人，如果想要at全员，请传入all
     */
    public static void sendTextWithTitle(String robotUrl, String title, CharSequence content, String... at) {
        if (null == title || title.isEmpty() || null == content) {
            return;
        }
        String pidAndHostname = ManagementFactory.getRuntimeMXBean().getName();
        int idx = pidAndHostname.indexOf('@');
        String hostname;
        if (idx > 0) {
            hostname = pidAndHostname.substring(idx + 1);
        } else {
            hostname = pidAndHostname;
        }
        String newTitle = title + "@" + hostname;
        String data = "**" + DbMap.escapeMarkdown(newTitle) + "**  \n  \n" + "***\n" + DbMap.escapeMarkdown(content);
        sendMarkdown(robotUrl, newTitle, data, at);
    }

    /**
     * 发送带标题的文本消息（内部使用markdown包装），可通过手机号指定@谁，通用格式，注意如果要@指定人需要在content中加入@手机号（异步发送）
     * <p>
     * 如果需要对content内容定制，请使用{@link #sendMarkdown(String, String, CharSequence, String...)}
     *
     * @param robotUrl 如果是加签安全设置的机器人，传入格式为 密钥,机器人URL
     * @param at       需要at的人，如果想要at全员，请传入all
     */
    public static void sendTextWithTitleAsync(String robotUrl, String title, CharSequence content, String... at) {
        Map<String, String> mdc = MDC.getCopyOfContextMap();
        executor.execute(() -> {
            try {
                if (null != mdc) {
                    MDC.setContextMap(mdc);
                }
                sendTextWithTitle(robotUrl, title, content, at);
            } finally {
                MDC.clear();
            }
        });
    }
}
